/*!
 * \file      rtc-board.h
 *
 * \brief     Target board RTC timer and low power modes management
 *
 * \copyright Revised BSD License, see section \ref LICENSE.
 *
 * \code
 *                ______                              _
 *               / _____)             _              | |
 *              ( (____  _____ ____ _| |_ _____  ____| |__
 *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 *               _____) ) ____| | | || |_| ____( (___| | | |
 *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
 *              (C)2013-2017 Semtech
 *
 * \endcode
 *
 * \author    Miguel Luis ( Semtech )
 *
 * \author    Gregory Cristian ( Semtech )
 */

#include <math.h>
#include "n32l43x_rtc.h"
#include "n32l43x.h"
#include <stdint.h>
#include <stdbool.h>
#include "timer.h"
#include "lpm-board.h"
#include "rtc-board.h"

// MCU Wake Up Time
#define MIN_ALARM_DELAY                             3 // in ticks

// sub-second number of bits
#define N_PREDIV_S                                  10

// Synchronous prediv
#define PREDIV_S                                    ( ( 1 << N_PREDIV_S ) - 1 )             // 1023

// Asynchronous prediv
#define PREDIV_A                                    ( 1 << ( 15 - N_PREDIV_S ) ) - 1        // 31

#define RTC_ALRMASSR_MASKSS_Pos                     24
// Sub-second mask definition
#define ALARM_SUBSECOND_MASK                        ( N_PREDIV_S << RTC_ALRMASSR_MASKSS_Pos ) // 

// RTC Time base in us
#define USEC_NUMBER                                 1000000
#define MSEC_NUMBER                                 ( USEC_NUMBER / 1000 )                  // 1000

#define COMMON_FACTOR                               3
#define CONV_NUMER                                  ( MSEC_NUMBER >> COMMON_FACTOR )        // 1000/8=125
#define CONV_DENOM                                  ( 1 << ( N_PREDIV_S - COMMON_FACTOR ) ) // 1<<7=128

/*!
 * \brief Days, Hours, Minutes and seconds
 */
#define DAYS_IN_LEAP_YEAR                           ( ( uint32_t )  366U )
#define DAYS_IN_YEAR                                ( ( uint32_t )  365U )
#define SECONDS_IN_1DAY                             ( ( uint32_t )86400U )
#define SECONDS_IN_1HOUR                            ( ( uint32_t ) 3600U )
#define SECONDS_IN_1MINUTE                          ( ( uint32_t )   60U )
#define MINUTES_IN_1HOUR                            ( ( uint32_t )   60U )
#define HOURS_IN_1DAY                               ( ( uint32_t )   24U )

/*!
 * \brief Correction factors
 */
#define  DAYS_IN_MONTH_CORRECTION_NORM              ( ( uint32_t )0x99AAA0 )
#define  DAYS_IN_MONTH_CORRECTION_LEAP              ( ( uint32_t )0x445550 )

/*!
 * \brief Calculates ceiling( X / N )
 */
#define DIVC( X, N )                                ( ( ( X ) + ( N ) -1 ) / ( N ) )

typedef struct {
    uint32_t Time;
    RTC_TimeType CalenderTime;
    RTC_DateType CalenderDate;
    uint32_t SubSecond;
}RtcTimerContext_t;

typedef enum {
    RTC_CLK_SRC_TYPE_HSE_DIV32=0x01,
    RTC_CLK_SRC_TYPE_LSE=0x02,
    RTC_CLK_SRC_TYPE_LSI=0x03,
}RTC_CLK_SRC_TYPE;

/*!
 * \brief Indicates if the RTC is already Initialized or not
 */
static bool RtcInitialized = false;

/*!
 * Number of days in each month on a normal year
 */
static const uint8_t DaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

/*!
 * Number of days in each month on a leap year
 */
static const uint8_t DaysInMonthLeapYear[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static RtcTimerContext_t RtcTimerContext;

uint64_t RtcGetCalendarValue(RtcTimerContext_t *timeContext);

/**
 * @brief  Configures the RTC Source Clock Type.
 * @param Clk_Src_Type specifies RTC Source Clock Type.
 *   This parameter can be on of the following values:
 *     @arg RTC_CLK_SRC_TYPE_HSE128
 *     @arg RTC_CLK_SRC_TYPE_LSE
 *     @arg RTC_CLK_SRC_TYPE_LSI
 * @param Is_First_Cfg_RCC specifies Is First Config RCC Module.
 *   This parameter can be on of the following values:
 *     @arg true
 *     @arg false
 * @param Is_Rst_Bkp specifies Whether Reset The Backup Area
 *   This parameter can be on of the following values:
 *     @arg true
 *     @arg false
 */

/**
 * @brief  RTC prescaler config.
 */
void RTC_Config(void)
{
    RTC_InitType  RTC_InitStructure;
    /* Configure the RTC data register and RTC prescaler */
    RTC_InitStructure.RTC_AsynchPrediv = PREDIV_A;
    RTC_InitStructure.RTC_SynchPrediv  = PREDIV_S;
    RTC_InitStructure.RTC_HourFormat   = RTC_24HOUR_FORMAT;
    /* Check on RTC init */
    if (RTC_Init(&RTC_InitStructure) == ERROR)
    {
//        log_info("\r\n //******* RTC Prescaler Config failed **********// \r\n");
    }
}
uint32_t RtcSetTimerContext( void );
/*!
 * \brief Initializes the RTC timer
 *
 * \remark The timer is based on the RTC
 */
void RtcInit( void )
{
    RTC_TimeType time;
    RTC_DateType date;
    if(RtcInitialized == false) {
        RTC_Config();
        date.Year       = 0;
        date.Month      = RTC_MONTH_JANUARY;
        date.Date       = 1;
        date.WeekDay    = RTC_WEEKDAY_MONDAY;
        RTC_SetDate(RTC_FORMAT_BIN, &date);
        time.Hours      = 0;
        time.Minutes    = 0;
        time.Seconds    = 0;
        time.H12        = 0;
        RTC_ConfigTime(RTC_FORMAT_BIN, &time);
        NVIC_InitType NVIC_InitStruct;
        EXTI_InitType EXTI_InitStructure;

        EXTI_ClrITPendBit(EXTI_LINE18);
        EXTI_InitStructure.EXTI_Line = EXTI_LINE18;
        #ifdef __TEST_SEVONPEND_WFE_NVIC_DIS__
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
        #else
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
        #endif
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_InitPeripheral(&EXTI_InitStructure);
        NVIC_InitStruct.NVIC_IRQChannel = RTCAlarm_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
        NVIC_Init(&NVIC_InitStruct);
        RTC_EnableAlarm(RTC_A_ALARM, DISABLE);
        RtcSetTimerContext();
        RtcInitialized = true;
    }
}

/*!
 * \brief Sets the RTC timer reference
 *
 * \retval value Timer reference value in ticks
 */
uint32_t RtcSetTimerContext( void )
{
    RtcTimerContext.Time = (uint32_t)RtcGetCalendarValue(&RtcTimerContext);
    return (uint32_t)RtcTimerContext.Time;
}
  
/*!
 * \brief Gets the RTC timer reference
 *
 * \retval value Timer value in ticks
 */
uint32_t RtcGetTimerContext( void )
{
    return (uint32_t)RtcTimerContext.Time;
}

/*!
 * \brief Returns the minimum timeout value
 *
 * \retval minTimeout Minimum timeout value in in ticks
 */
uint32_t RtcGetMinimumTimeout( void )
{
    return( MIN_ALARM_DELAY );
}

/*!
 * \brief converts time in ms to time in ticks
 *
 * \param[IN] milliseconds Time in milliseconds
 * \retval returns time in timer ticks
 */
uint32_t RtcMs2Tick( TimerTime_t milliseconds )
{
    return ( uint32_t )( ( ( ( uint64_t )milliseconds ) * CONV_DENOM ) / CONV_NUMER );
}

/*.!
 * \brief converts time in ticks to time in ms
 *
 * \param[IN] time in timer ticks
 * \retval returns time in milliseconds
 */
TimerTime_t RtcTick2Ms( uint32_t tick )
{
    uint32_t seconds = tick >> N_PREDIV_S;

    tick = tick & PREDIV_S;
    return ( ( seconds * 1000 ) + ( ( tick * 1000 ) >> N_PREDIV_S ) );
}

/*!
 * \brief Performs a delay of milliseconds by polling RTC
 *
 * \param[IN] milliseconds Delay in ms
 */
void RtcDelayMs( TimerTime_t milliseconds )
{
    uint64_t delayTicks = 0;
    uint64_t refTicks = RtcGetTimerValue( );

    delayTicks = RtcMs2Tick( milliseconds );

    // Wait delay ms
    while( ( ( RtcGetTimerValue( ) - refTicks ) ) < delayTicks )
    {
        __NOP( );
    }
}

/*!
 * \brief Sets the alarm
 *
 * \note The alarm is set at now (read in this funtion) + timeout
 *
 * \param timeout [IN] Duration of the Timer ticks
 */
void RtcSetAlarm( uint32_t timeout )
{
    // We don't go in Low Power mode for timeout below MIN_ALARM_DELAY
    if( ( int64_t )MIN_ALARM_DELAY < ( int64_t )( timeout - RtcGetTimerElapsedTime( ) ) )
    {
        LpmSetStopMode( LPM_RTC_ID, LPM_ENABLE );
    }
    else
    {
        LpmSetStopMode( LPM_RTC_ID, LPM_DISABLE );
    }

    RtcStartAlarm( timeout );
}

/*!
 * \brief Stops the Alarm
 */
void RtcStopAlarm( void )
{
    RTC_EnableAlarm(RTC_A_ALARM, DISABLE);
    RTC_ClrFlag(RTC_FLAG_ALAF);
    RTC_ClrIntPendingBit(RTC_INT_ALRA);
    EXTI_ClrITPendBit(EXTI_LINE18);
}

/*!
 * \brief Starts wake up alarm
 *
 * \note  Alarm in RtcTimerContext.Time + timeout
 *
 * \param [IN] timeout Timeout value in ticks
 */
void RtcStartAlarm( uint32_t timeout )
{
    uint16_t rtcAlarmSubSeconds = 0;
    uint16_t rtcAlarmSeconds = 0;
    uint16_t rtcAlarmMinutes = 0;
    uint16_t rtcAlarmHours = 0;
    uint16_t rtcAlarmDays = 0;
    RTC_TimeType time = RtcTimerContext.CalenderTime;
    RTC_DateType date = RtcTimerContext.CalenderDate;
    
    RtcStopAlarm();
    
    rtcAlarmSubSeconds = PREDIV_S - RtcTimerContext.SubSecond;
    rtcAlarmSubSeconds += (timeout & PREDIV_S);
    timeout >>= N_PREDIV_S;
    
    rtcAlarmDays = date.Date;
    while(timeout >= SECONDS_IN_1DAY) {
        timeout -= SECONDS_IN_1DAY;
        rtcAlarmDays ++;
    }
    
    rtcAlarmHours = time.Hours;
    while(timeout >= SECONDS_IN_1HOUR) {
        timeout -= SECONDS_IN_1HOUR;
        rtcAlarmHours ++;
    }
    
    rtcAlarmMinutes = time.Minutes;
    while(timeout >= SECONDS_IN_1MINUTE) {
        timeout -= SECONDS_IN_1MINUTE;
        rtcAlarmMinutes ++;
    }
    
    rtcAlarmSeconds = time.Seconds + timeout;
    while(rtcAlarmSubSeconds >= (PREDIV_S + 1)) {
        rtcAlarmSubSeconds -= (PREDIV_S + 1);
        rtcAlarmSeconds ++;
    }
    
    while( rtcAlarmSeconds >= SECONDS_IN_1MINUTE )
    { 
        rtcAlarmSeconds -= SECONDS_IN_1MINUTE;
        rtcAlarmMinutes++;
    }

    while( rtcAlarmMinutes >= MINUTES_IN_1HOUR )
    {
        rtcAlarmMinutes -= MINUTES_IN_1HOUR;
        rtcAlarmHours++;
    }

    while( rtcAlarmHours >= HOURS_IN_1DAY )
    {
        rtcAlarmHours -= HOURS_IN_1DAY;
        rtcAlarmDays++;
    }
    
    if( date.Year % 4 == 0 ) 
    {
        if( rtcAlarmDays > DaysInMonthLeapYear[date.Month - 1] )
        {
            rtcAlarmDays = rtcAlarmDays % DaysInMonthLeapYear[date.Month - 1];
        }
    }
    else
    {
        if( rtcAlarmDays > DaysInMonth[date.Month - 1] )
        {   
            rtcAlarmDays = rtcAlarmDays % DaysInMonth[date.Month - 1];
        }
    }
    
    RTC_AlarmType rtcAlarm;
    rtcAlarm.AlarmTime.Seconds = rtcAlarmSeconds;
    rtcAlarm.AlarmTime.Minutes = rtcAlarmMinutes;
    rtcAlarm.AlarmTime.Hours = rtcAlarmHours;
    rtcAlarm.AlarmTime.H12 = RTC_AM_H12;
    rtcAlarm.AlarmMask = RTC_ALARMMASK_NONE;
    rtcAlarm.DateWeekValue = rtcAlarmDays;
    rtcAlarm.DateWeekMode = RTC_ALARM_SEL_WEEKDAY_DATE;
    RTC_SetAlarm(RTC_FORMAT_BIN, RTC_A_ALARM, &rtcAlarm);
    RTC_ConfigAlarmSubSecond(RTC_A_ALARM, PREDIV_S - rtcAlarmSubSeconds, ALARM_SUBSECOND_MASK);
    RTC_ConfigInt(RTC_INT_ALRA, ENABLE);
    RTC_EnableAlarm(RTC_A_ALARM, ENABLE);
}

/*!
 * \brief Get the RTC timer value
 *
 * \retval RTC Timer value
 */
uint32_t RtcGetTimerValue( void )
{
    RtcTimerContext_t timeContext;
    uint32_t calendarValue = (uint32_t)RtcGetCalendarValue(&timeContext);
    return (uint32_t)calendarValue;
}

/*!
 * \brief Get the RTC timer elapsed time since the last Alarm was set
 *
 * \retval RTC Elapsed time since the last alarm in ticks.
 */
uint32_t RtcGetTimerElapsedTime( void )
{
    RtcTimerContext_t timeContext;
    uint32_t calendarValue = (uint32_t)RtcGetCalendarValue(&timeContext);
    return (uint32_t)(calendarValue - RtcTimerContext.Time);
}

static uint64_t RtcGetCalendarValue(RtcTimerContext_t *timeContext)
{
    uint64_t calendarValue = 0;
    uint32_t correction;
    uint32_t seconds;
    uint32_t subSeconds;
    
    RTC_TimeType *time = &timeContext->CalenderTime;
    RTC_DateType *date = &timeContext->CalenderDate;
    
    do{
        subSeconds = RTC->SUBS;
        RTC_GetDate(RTC_FORMAT_BIN, date);
        RTC_GetTime(RTC_FORMAT_BIN, time);
    }while(subSeconds != RTC->SUBS);
    
    timeContext->SubSecond = subSeconds;
    
    // Calculte amount of elapsed days since 01/01/2000
    seconds = DIVC( ( DAYS_IN_YEAR * 3 + DAYS_IN_LEAP_YEAR ) * date->Year , 4 );

    correction = ( ( date->Year % 4 ) == 0 ) ? DAYS_IN_MONTH_CORRECTION_LEAP : DAYS_IN_MONTH_CORRECTION_NORM;

    seconds += ( DIVC( ( date->Month-1 ) * ( 30 + 31 ), 2 ) - ( ( ( correction >> ( ( date->Month - 1 ) * 2 ) ) & 0x03 ) ) );

    seconds += ( date->Date -1 );

    // Convert from days to seconds
    seconds *= SECONDS_IN_1DAY;

    seconds += ( ( uint32_t )time->Seconds + 
                 ( ( uint32_t )time->Minutes * SECONDS_IN_1MINUTE ) +
                 ( ( uint32_t )time->Hours * SECONDS_IN_1HOUR ) ) ;

    calendarValue = ( ( ( uint64_t )seconds ) << N_PREDIV_S ) + ( PREDIV_S - subSeconds );

    return( calendarValue );
}

/*!
 * \brief Gets the system time with the number of seconds elapsed since epoch
 *
 * \param [OUT] milliseconds Number of milliseconds elapsed since epoch
 * \retval seconds Number of seconds elapsed since epoch
 */
uint32_t RtcGetCalendarTime( uint16_t *milliseconds )
{
    RtcTimerContext_t timeContext;
    uint32_t ticks;
    
    uint64_t calendarValue = RtcGetCalendarValue(&timeContext);
    uint32_t seconds = (uint32_t)(calendarValue >> N_PREDIV_S);
    ticks = (uint32_t)calendarValue & PREDIV_S;
    
    *milliseconds = RtcTick2Ms(ticks);
    
    return seconds;
}

//TimerIrqHandler
//void RTC_AlarmAEventCallback()
//{
//    TimerIrqHandler();
//}

void RTCAlarm_IRQHandler()
{
    LpmSetStopMode(LPM_RTC_ID, LPM_ENABLE);
    if (RTC_GetITStatus(RTC_INT_ALRA) != RESET)
    {
        RTC_ClrIntPendingBit(RTC_INT_ALRA);
        EXTI_ClrITPendBit(EXTI_LINE18);
//        RTC_AlarmAEventCallback();
        TimerIrqHandler();
    }
}

/*!
 * \brief Writes data0 and data1 to the RTC backup registers
 *
 * \param [IN] data0 1st Data to be written
 * \param [IN] data1 2nd Data to be written
 */
void RtcBkupWrite( uint32_t data0, uint32_t data1 )
{
    RTC->BKP1R = data0;
    RTC->BKP2R = data1;
}

/*!
 * \brief Reads data0 and data1 from the RTC backup registers
 *
 * \param [OUT] data0 1st Data to be read
 * \param [OUT] data1 2nd Data to be read
 */
void RtcBkupRead( uint32_t* data0, uint32_t* data1 )
{
    *data0 = RTC->BKP1R;
    *data1 = RTC->BKP2R;
}

/*!
 * \brief Processes pending timer events
 */
void RtcProcess( void )
{
}

/*!
 * \brief Computes the temperature compensation for a period of time on a
 *        specific temperature.
 *
 * \param [IN] period Time period to compensate in milliseconds
 * \param [IN] temperature Current temperature
 *
 * \retval Compensated time period
 */
TimerTime_t RtcTempCompensation( TimerTime_t period, float temperature )
{
    float k = RTC_TEMP_COEFFICIENT;
    float kDev = RTC_TEMP_DEV_COEFFICIENT;
    float t = RTC_TEMP_TURNOVER;
    float tDev = RTC_TEMP_DEV_TURNOVER;
    float interim = 0.0f;
    float ppm = 0.0f;

    if( k < 0.0f )
    {
        ppm = ( k - kDev );
    }
    else
    {
        ppm = ( k + kDev );
    }
    interim = ( temperature - ( t - tDev ) );
    ppm *=  interim * interim;

    // Calculate the drift in time
    interim = ( ( float ) period * ppm ) / 1000000.0f;
    // Calculate the resulting time period
    interim += period;
    interim = floor( interim );

    if( interim < 0.0f )
    {
        interim = ( float )period;
    }

    // Calculate the resulting period
    return ( TimerTime_t ) interim;
}


